//
// Created by 常磊 on 2022/4/4.
//

import Foundation
import Alamofire

open class Dio {
    public typealias RequestModifier = Session.RequestModifier
    
    public static let `default` = Dio()
    
    private let core: Session
    
    public var session: URLSession {
        core.session
    }
    
    public var delegate: SessionDelegate {
        core.delegate
    }
    
    public var rootQueue: DispatchQueue {
        core.rootQueue
    }
    
    public var startRequestsImmediately: Bool {
        core.startRequestsImmediately
    }
    
    public var requestQueue: DispatchQueue {
        core.requestQueue
    }
    
    public var serializationQueue: DispatchQueue {
        core.serializationQueue
    }
    
    public var interceptor: RequestInterceptor? {
        core.interceptor
    }
    
    public var serverTrustManager: ServerTrustManager? {
        core.serverTrustManager
    }
    
    public var redirectHandler: RedirectHandler? {
        core.redirectHandler
    }
    
    public var cachedResponseHandler: CachedResponseHandler? {
        core.cachedResponseHandler
    }
    
    public var eventMonitor: CompositeEventMonitor {
        core.eventMonitor
    }
    
    public var defaultEventMonitors: [EventMonitor] {
        core.defaultEventMonitors
    }
    
    public init(
        session: URLSession,
        delegate: SessionDelegate,
        rootQueue: DispatchQueue,
        startRequestsImmediately: Bool = true,
        requestQueue: DispatchQueue? = nil,
        serializationQueue: DispatchQueue? = nil,
        interceptor: RequestInterceptor? = nil,
        serverTrustManager: ServerTrustManager? = nil,
        redirectHandler: RedirectHandler? = nil,
        cachedResponseHandler: CachedResponseHandler? = nil,
        eventMonitors: [EventMonitor] = []
    ) {
        core = Session.init(
            session: session,
            delegate: delegate,
            rootQueue: rootQueue,
            startRequestsImmediately: startRequestsImmediately,
            requestQueue: requestQueue,
            serializationQueue: serializationQueue,
            interceptor: interceptor,
            serverTrustManager: serverTrustManager,
            redirectHandler: redirectHandler,
            cachedResponseHandler: cachedResponseHandler,
            eventMonitors: eventMonitors
        )
    }
    
    public convenience init(
        configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
        delegate: SessionDelegate = SessionDelegate(),
        rootQueue: DispatchQueue = DispatchQueue(label: "org.dio.session.rootQueue"),
        startRequestsImmediately: Bool = true,
        requestQueue: DispatchQueue? = nil,
        serializationQueue: DispatchQueue? = nil,
        interceptor: RequestInterceptor? = nil,
        serverTrustManager: ServerTrustManager? = nil,
        redirectHandler: RedirectHandler? = nil,
        cachedResponseHandler: CachedResponseHandler? = nil,
        eventMonitors: [EventMonitor] = []
    ) {
        precondition(configuration.identifier == nil, "Dio does not support background URLSessionConfigurations.")
        
        // Retarget the incoming rootQueue for safety, unless it's the main queue, which we know is safe.
        let serialRootQueue = (rootQueue === DispatchQueue.main) ? rootQueue : DispatchQueue(label: rootQueue.label, target: rootQueue)
        let delegateQueue = OperationQueue()
        delegateQueue.maxConcurrentOperationCount = 1
        delegateQueue.underlyingQueue = serialRootQueue
        delegateQueue.name = "\(serialRootQueue.label).sessionDelegate"
        let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
        
        self.init(
            session: session,
            delegate: delegate,
            rootQueue: serialRootQueue,
            startRequestsImmediately: startRequestsImmediately,
            requestQueue: requestQueue,
            serializationQueue: serializationQueue,
            interceptor: interceptor,
            serverTrustManager: serverTrustManager,
            redirectHandler: redirectHandler,
            cachedResponseHandler: cachedResponseHandler,
            eventMonitors: eventMonitors
        )
    }
    
    public func withAllRequests(
        perform action: @escaping (Set<Request>) -> Void
    ) {
        core.withAllRequests(
            perform: action
        )
    }
    
    public func cancelAllRequests(
        completingOnQueue queue: DispatchQueue = .main,
        completion: (() -> Void)? = nil
    ) {
        core.cancelAllRequests(
            completingOnQueue: queue,
            completion: completion
        )
    }
    
    open func request(
        _ convertible: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        requestModifier: RequestModifier? = nil
    ) -> DioDataRequest {
        DioDataRequest(
            core.request(
                convertible,
                method: method,
                parameters: parameters,
                encoding: encoding,
                headers: headers,
                interceptor: interceptor,
                requestModifier: requestModifier
            )
        )
    }
    
    open func request<Parameters: Encodable>(
        _ convertible: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        requestModifier: RequestModifier? = nil
    ) -> DioDataRequest {
        DioDataRequest(
            core.request(
                convertible,
                method: method,
                parameters: parameters,
                encoder: encoder,
                headers: headers,
                interceptor: interceptor,
                requestModifier: requestModifier
            )
        )
    }
    
    open func request(
        _ convertible: URLRequestConvertible,
        interceptor: RequestInterceptor? = nil
    ) -> DioDataRequest {
        DioDataRequest(
            core.request(
                convertible,
                interceptor: interceptor
            )
        )
    }
    
    open func streamRequest<Parameters: Encodable>(
        _ convertible: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
        headers: HTTPHeaders? = nil,
        automaticallyCancelOnStreamError: Bool = false,
        interceptor: RequestInterceptor? = nil,
        requestModifier: RequestModifier? = nil
    ) -> DioDataStreamRequest {
        DioDataStreamRequest(
            core.streamRequest(
                convertible,
                method: method,
                parameters: parameters,
                encoder: encoder,
                headers: headers,
                automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
                interceptor: interceptor,
                requestModifier: requestModifier
            )
        )
    }
    
    open func streamRequest(
        _ convertible: URLConvertible,
        method: HTTPMethod = .get,
        headers: HTTPHeaders? = nil,
        automaticallyCancelOnStreamError: Bool = false,
        interceptor: RequestInterceptor? = nil,
        requestModifier: RequestModifier? = nil
    ) -> DioDataStreamRequest {
        DioDataStreamRequest(
            core.streamRequest(
                convertible,
                method: method,
                headers: headers,
                automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
                interceptor: interceptor,
                requestModifier: requestModifier
            )
        )
    }
    
    open func streamRequest(
        _ convertible: URLRequestConvertible,
        automaticallyCancelOnStreamError: Bool = false,
        interceptor: RequestInterceptor? = nil
    ) -> DioDataStreamRequest {
        DioDataStreamRequest(
            core.streamRequest(
                convertible,
                automaticallyCancelOnStreamError: automaticallyCancelOnStreamError,
                interceptor: interceptor
            )
        )
    }
    
    open func download(
        _ convertible: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        requestModifier: RequestModifier? = nil,
        to destination: DownloadRequest.Destination? = nil
    ) -> DioDownloadRequest {
        DioDownloadRequest(
            core.download(
                convertible,
                method: method,
                parameters: parameters,
                encoding: encoding,
                headers: headers,
                interceptor: interceptor,
                requestModifier: requestModifier,
                to: destination
            )
        )
    }
    
    open func download<Parameters: Encodable>(
        _ convertible: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        requestModifier: RequestModifier? = nil,
        to destination: DownloadRequest.Destination? = nil
    ) -> DioDownloadRequest {
        DioDownloadRequest(
            core.download(
                convertible,
                method: method,
                parameters: parameters,
                encoder: encoder,
                headers: headers,
                interceptor: interceptor,
                requestModifier: requestModifier,
                to: destination
            )
        )
    }
    
    open func download(
        _ convertible: URLRequestConvertible,
        interceptor: RequestInterceptor? = nil,
        to destination: DownloadRequest.Destination? = nil
    ) -> DioDownloadRequest {
        DioDownloadRequest(
            core.download(
                convertible,
                interceptor: interceptor,
                to: destination
            )
        )
    }
    
    open func download(
        resumingWith data: Data,
        interceptor: RequestInterceptor? = nil,
        to destination: DownloadRequest.Destination? = nil
    ) -> DioDownloadRequest {
        DioDownloadRequest(
            core.download(
                resumingWith: data,
                interceptor: interceptor,
                to: destination
            )
        )
    }
    
    open func upload(
        _ data: Data,
        to convertible: URLConvertible,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default,
        requestModifier: RequestModifier? = nil
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                data,
                to: convertible,
                method: method,
                headers: headers,
                interceptor: interceptor,
                fileManager: fileManager,
                requestModifier: requestModifier
            )
        )
    }
    
    open func upload(
        _ data: Data,
        with convertible: URLRequestConvertible,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                data,
                with: convertible,
                interceptor: interceptor,
                fileManager: fileManager
            )
        )
    }
    
    open func upload(
        _ fileURL: URL,
        to convertible: URLConvertible,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default,
        requestModifier: RequestModifier? = nil
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                fileURL,
                to: convertible,
                method: method,
                headers: headers,
                interceptor: interceptor,
                fileManager: fileManager,
                requestModifier: requestModifier
            )
        )
    }
    
    open func upload(
        _ fileURL: URL,
        with convertible: URLRequestConvertible,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                fileURL,
                with: convertible,
                interceptor: interceptor,
                fileManager: fileManager
            )
        )
    }
    
    open func upload(
        _ stream: InputStream,
        to convertible: URLConvertible,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default,
        requestModifier: RequestModifier? = nil
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                stream,
                to: convertible,
                method: method,
                headers: headers,
                interceptor: interceptor,
                fileManager: fileManager,
                requestModifier: requestModifier
            )
        )
    }
    
    open func upload(
        _ stream: InputStream,
        with convertible: URLRequestConvertible,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                stream,
                with: convertible,
                interceptor: interceptor,
                fileManager: fileManager
            )
        )
    }
    
    open func upload(
        multipartFormData: @escaping (MultipartFormData) -> Void,
        to url: URLConvertible,
        usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default,
        requestModifier: RequestModifier? = nil
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                multipartFormData: multipartFormData,
                to: url,
                usingThreshold: encodingMemoryThreshold,
                method: method,
                headers: headers,
                interceptor: interceptor,
                fileManager: fileManager,
                requestModifier: requestModifier
            )
        )
    }
    
    open func upload(
        multipartFormData: @escaping (MultipartFormData) -> Void,
        with request: URLRequestConvertible,
        usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                multipartFormData: multipartFormData,
                with: request,
                usingThreshold: encodingMemoryThreshold,
                interceptor: interceptor,
                fileManager: fileManager
            )
        )
    }
    
    open func upload(
        multipartFormData: MultipartFormData,
        to url: URLConvertible,
        usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
        method: HTTPMethod = .post,
        headers: HTTPHeaders? = nil,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default,
        requestModifier: RequestModifier? = nil
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                multipartFormData: multipartFormData,
                to: url,
                usingThreshold: encodingMemoryThreshold,
                method: method,
                headers: headers,
                interceptor: interceptor,
                fileManager: fileManager,
                requestModifier: requestModifier
            )
        )
    }
    
    open func upload(
        multipartFormData: MultipartFormData,
        with request: URLRequestConvertible,
        usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
        interceptor: RequestInterceptor? = nil,
        fileManager: FileManager = .default
    ) -> DioUploadRequest {
        DioUploadRequest(
            core.upload(
                multipartFormData: multipartFormData,
                with: request,
                usingThreshold: encodingMemoryThreshold,
                interceptor: interceptor,
                fileManager: fileManager
            )
        )
    }
}

// MARK: - RequestDelegate

extension Dio: DioRequestDelegate, RequestDelegate {
    private var assignor: RequestAssignor {
        RequestAssignor(core)!
    }
    
    public var sessionConfiguration: URLSessionConfiguration {
        assignor.sessionConfiguration
    }
    
    public var startImmediately: Bool {
        assignor.startImmediately
    }
    
    public func cleanup(after request: DioRequest) {
        assignor.cleanup(after: request)
    }
    
    public func retryRequest(_ request: DioRequest, withDelay timeDelay: TimeInterval?) {
        assignor.retryRequest(request, withDelay: timeDelay)
    }
    
    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
    public func retryResult(for request: DioRequest, dueTo error: DioError) async -> RetryResult {
        await assignor.retryResult(for: request, dueTo: error)
    }
    
    public func cleanup(after request: Request) {
        assignor.cleanup(after: request)
    }
    
    public func retryResult(for request: Request, dueTo error: DioError, completion: @escaping (RetryResult) -> Void) {
        assignor.retryResult(for: request, dueTo: error, completion: completion)
    }
    
    public func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) {
        assignor.retryRequest(request, withDelay: timeDelay)
    }
}
